﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
using System.IO;
using System.IO.Compression;
using ArgusPCLib.Xml;

namespace ArgusPCLib.Bird
{
	public delegate void ExitEventHandler();

	public class Interface
	{
		const string entryLock = "lock";
		const string entryLog = "Log.xml";

		static readonly string[] PlatformAssemblies = new string[] { "ArgusPCLib.Bird.WindowsDesktop", "ArgusPCLib.Bird.WinRT", "ArgusPCLib.Bird.WinPhone" };

		public static Interface Instance { get; private set; }

		static Interface()
		{
			Interface.Instance = new Interface();
		}

		ZipArchive zipArchive;
		string baseDir;
		IPlatformInterface platformInterface;
		bool _initializedSuccessfully;
		Task<bool> initializerTask;
		bool skipCleanUp = false;

		public bool InitializedSuccessfully
		{
			get
			{
				lock (this)
				{
					if (this.initializerTask == null)
						return this._initializedSuccessfully;

					this._initializedSuccessfully = this.initializerTask.Result;
					this.initializerTask = null;
					return this._initializedSuccessfully;
				}
			}
		}

		public event UnhandledExceptionEventHandler UnhandledException;
		public event CrashDetectedEventHandler CrashDetected;
		public event BugReportSentEventHandler BugReportSent;

		Interface()
		{
			this.initializerTask = this.InitializeCore();
		}

		async Task<bool> InitializeCore()
		{
			Assembly assembly = null;

			foreach (string platformAssembly in Interface.PlatformAssemblies)
			{
				AssemblyName assemblyName = new AssemblyName(platformAssembly);
				try { assembly = Assembly.Load(assemblyName); }
				catch { continue; }
				break;
			}

			if (assembly == null)
				return false;

			Type type = assembly.GetType(assembly.GetName().Name + ".Interface");
			if (type == null)
				return false;

			this.platformInterface = Activator.CreateInstance(type) as IPlatformInterface;
			if (platformInterface == null)
				return false;

			this.zipArchive = new ZipArchive(this.platformInterface.GetUserDataStream(), ZipArchiveMode.Update, false);
			if (this.zipArchive.GetEntry(Interface.entryLock) != null)
				await this.OnCrashDetected();
			else
				this.zipArchive.CreateEntry(Interface.entryLock);

			this.baseDir = DateTime.UtcNow.Ticks.ToString("X") + '/';

			if (!Logger.IsLogWriterAvailable)
			{
				ZipArchiveEntry entry = this.zipArchive.CreateEntry(this.baseDir + Interface.entryLog);
				Logger.LogWriter = new LogXmlWriter(entry.Open());
			}

			this.platformInterface.RegisterUnhandledExceptionEventHandler(this.ReceiveUnhandledException);
			this.platformInterface.RegisterExitEventHandler(this.OnExit);
			return true;
		}

		async Task OnCrashDetected()
		{
			CrashDetectedEventArgs e = new CrashDetectedEventArgs("BugReport", "");
			if (this.CrashDetected != null)
				this.CrashDetected(this, e);
			if (e.ServerAdress != null)
			{
				this.zipArchive.Dispose();
				this.zipArchive = null;
				Stream stream = this.platformInterface.GetUserDataStream();
				stream.Seek(0, SeekOrigin.Begin);
				byte[] data = new byte[(int)stream.Length];

				if (await stream.ReadAsync(data, 0, data.Length) > -1)
				{
					ServerResponses serverResponse = await MailServer.Send(e.ServerAdress, e.MailSubject
						, e.MailMessageTxt, e.MailMessageHtml, data, e.MailSubject+".zip");

					if (serverResponse == ServerResponses.SuccessResponse)
						stream.SetLength(0);

					if (this.BugReportSent != null)
						this.BugReportSent(this, new BugReportSentEventArgs(serverResponse));
				}

				this.zipArchive = new ZipArchive(stream, ZipArchiveMode.Update);
				if (this.zipArchive.GetEntry(Interface.entryLock) == null)
					this.zipArchive.CreateEntry(Interface.entryLock);
			}
		}

		void ReceiveUnhandledException(object sender, UnhandledExceptionEventArgs e)
		{
			lock (this)
			{
				if (!this.InitializedSuccessfully)
					return;

				this.skipCleanUp = true;

				using (Stream stream = this.zipArchive.CreateEntry(this.baseDir + "Exception.xml").Open())
				{
					using (Serializer serializer = Serializer.FromStream(stream, "Exception"))
					{
						serializer.Serialize(e.ExceptionObject);
					}
				}

				this.platformInterface.AddScreenshots((name) =>
					{
						return this.zipArchive.CreateEntry(this.baseDir + "Screenshots/" + name).Open();
					});
			}

			if (this.UnhandledException != null)
				this.UnhandledException(this, e);
		}

		void OnExit()
		{
			lock(this)
			{
				if (this.skipCleanUp || !this.InitializedSuccessfully)
					return;

				this.skipCleanUp = true;

				ZipArchiveEntry lockEntry = this.zipArchive.GetEntry(Interface.entryLock);
				if (lockEntry != null)
					lockEntry.Delete();

				this.zipArchive.Dispose();
				this.platformInterface.Dispose();
			}
		}
	}
}
